/*
 * Copyright the original author or authors.
 * 
 * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.mozilla.org/MPL/MPL-1.1.html
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.kairos.engine 
{
	import flash.events.Event;
	
	import com.bourre.commands.AbstractSyncCommand;
	import com.bourre.error.NullPointerException;
	import com.bourre.error.UnsupportedOperationException;
	import com.bourre.utils.ClassUtils;
	import com.kairos.log.KairosDebug;		

	/**
	 * Abstract object to create real-time based objects.
	 * 
	 * <p><code>RTObject</code> provide basic methods to start and stop
	 * animations within Kairos.</p>
	 *	
	 * @author	Cédric Néhémie
	 * @version	1.0	
	 * @example Building a simple class that move an object on X axis based on the timestep
	 * dispatched by the RTBeacon and modified by the local speed factor.
	 * <br/>Here moves are write in px/s.
	 * 
	 * <listing>import com.bourre.transitions.TickListener;

	 * class Mover extends RTObject implements TickListener
	 * {
	 * 	protected var _oTarget : MovieClip; // Object to move.
	 * 	protected var _nSpeed : Number; // Speed in px/s.
	 * 
	 * 	public function Mover ( o : MovieClip, speed : Number = 1 )
	 * 	{
	 * 		super( abstractRTObjectConstructorAccess );
	 * 
	 * 		_nSpeed = speed;
	 *		_oTarget = o;
	 * 			
	 * 		// start the animation
	 * 		start();
	 * 	} 
	 * 	public override function onTick ( e : Event = null ) : void
	 * 	{
	 * 		// current speed computed with the getLocalStepInSecond() return value
	 * 		var curSpeed : Number = _nSpeed * getLocalStepInSecond( e ); 
	 * 
	 * 		_oTarget.x += curSpeed;
	 * 	}
	 * }
	 * </listing>
	 */
	public class RTObject extends AbstractSyncCommand implements RTAnimation
	{
		
		/**
		 * A reference to the beacon with which it register/unregister.
		 * 
		 * <p>The passed-in beacon have to be a <code>RTBeacon</code> object
		 * or any subclasses which dispatch a <code>RTEvent</code> in the
		 * <code>onTick</code> event.
		 */
		protected var _oBeacon : RTBeacon;
		
		/**
		 * A speed multiplier for the current object. 
		 * 
		 * <p>Use that value in your algorythm if your object support
		 * the <code>setLocalSpeed</code> method of the <code>RTAnimation</code>
		 * interface. By default the local speed is set to 1.</p>
		 * 
		 * @example	Modify the values received from the beacon with the
		 * local speed factor.
		 * 
		 * <listing>public function onTick ( e : Event = null ) : void
		 * {
		 * 	var step : Number = ( e as RTEvent ).getStep() * _nLocalSpeed;
		 * 
		 * 	// do whatever you want
		 * }</listing>
		 */
		protected var _nLocalSpeed : Number;
		
		
		/**
		 * Initializes the object, by default it's not playing. 
		 * 
		 * <p>The <code>onTick</code> method is considered as virtual, that
		 * mean that if you don't override that method in your subclass, the super
		 * constructor will throw an <code>UnimplementedMethodException</code>.<p>
		 * 
		 * @throws	<code>UnimplementedMethodException</code> — The <code>onTick</code> method
		 * 			haven't been override by the current concrete class.
		 */
		public function RTObject ()	
		{
			ClassUtils.isImplemented( this, "com.kairos.engine:RTObject", "onTick" );
			
			_bIsRunning = false;
			_oBeacon = RTBeacon.getInstance();
			_nLocalSpeed = 1;
		}
		
		/**
		 * Implementation of the <code>TickListener</code> interface. 
		 * 
		 * <p>The <code>onTick</code> method is considered as virtual, that
		 * mean that if you don't override that method in your subclass, the super
		 * constructor will throw an <code>UnimplementedMethodException</code>.<p>
		 * 
		 * <p>Override it to create your own object, the <code>onTick</code>
		 * method is called with a <code>RTEvent</code> object as argument.</p>
		 * 
		 * param 	e 	<code>Event</code> of <code>TickListener</code> implementation.
		 */
		public function onTick ( e : Event = null ) : void 
		{}
		
		/**
		 * <code>start</code> alias.
		 *	
		 * @param	e	<code>Command</code> implementation
		 * @see		#start()
		 */
		override public function execute( e : Event = null ) : void 
		{
			start();
		}
		
		/**
		 * Starts the animation.
		 * 
		 * <p>If the object is allready playing the function don't
		 * do anything.</p>
	  	 */
		public function start () : void
		{
			if( !_bIsRunning )
			{
				_bIsRunning = true;
				_oBeacon.addTickListener ( this );
			}
		}
		
		/**
		 * <code>start</code> alias.
		 * 
		 * @see	#start
	  	 */
		public function play () : void
		{
			start();
		}
		
		/**
		 * Stops the animation.
		 * 
		 * <p>If the object is allready stopped the function don't
		 * do anything.</p>
		 */
		public function stop () : void
		{
			if( _bIsRunning )
			{
				_bIsRunning = false;
				_oBeacon.removeTickListener ( this );
			}
		}
		
		/**
		 * Overrides that method to create your own reset routine.
		 */		
		public function reset () : void
		{
			throw new UnsupportedOperationException ( "The reset method of the Suspendable interface is not supported by RTObject" );
		}
		
		/**
		 * Defines a new <code>TickBeacon</code> object the object will run on.
		 * 
		 * <p>Use that method carefully, if you have objects in your scene which 
		 * are plugged on two beacon which have differents settings you can loose
		 * synchronisation between those objects.</p>
		 * 
		 * <p>If the passed-in beacon is null, the function failed and throw an error
		 * on the Kairos log channel.</p>
		 * 
		 * @param 	beacon <code>TickBeacon</code> to register with.
		 * @throws	<code>NullPointerException</code> — The passed-in beacon is null.
		 */
		public function setTickBeacon( beacon : RTBeacon ) : void
		{
			if( !beacon ) 
			{
				KairosDebug.ERROR( "Trying to set a null beacon for " + this );
				throw new NullPointerException ( "Trying to set a null beacon for " + this );
				return; 
			}
			
			if( _bIsRunning )
			{ 
				stop();
				_oBeacon = beacon;
				start();
			}
			else
			{
				_oBeacon = beacon;
			}
		}
		
		/**
		 * Returns the <code>RTBeacon</code> used by the current object.
		 * 
		 * @return <code>RTBeacon</code> used by the current object.
		 */
		public function getTickBeacon () : RTBeacon
		{
			return _oBeacon;
		}
		 
		/**
		 * Defines the local speed factor for the current object. Local speed works
		 * as a multiplier for the time step value received from the beacon - as the
		 * <code>setGlobalSpeed</code> feature of the RTBeacon.
		 * 
		 * <p>The default <code>setLocalSpeed</code> method don't support value lower
		 * or equals than 0, override it if you want to avoid negative values.</p>
		 * 
		 * @param 	rate float number used as multiplier for the current time step
		 * @throws	<code>RangeError</code> — The value specified in <code>setLocalSpeed</code>
		 * 		   	isn't supported by the current object.	
		 */
		public function setLocalSpeed ( rate : Number ) : void
		{
			if( rate <= 0 )
			{
				KairosDebug.ERROR( rate + " can't be lower or equals than 0 in " + this + ".setLocalSpeed()" );
				throw new RangeError (" The value specified in setLocalSpeed isn't supported by the current object." );	
			}
			_nLocalSpeed = rate;
		}
		
		/**
		 * Returns the current local speed factor of the object.
		 * 
		 * @return <code>Number</code> local speed of the object.
		 */
		public function getLocalSpeed () : Number
		{
			return _nLocalSpeed;
		}
		
		/**
		 * Returns the time step passed in the <code>onTick</code> events
		 * in local timescale.
		 * 
		 * <p>Use that method in the <code>onTick</code> method to automatically
		 * convert the time step from global to local time scale. The example below show
		 * how simple it is to get the real step value to use in your equations.</p>
		 * 
		 * <listing>public override function onTick ( e : Event = null ) : void
		 * {
		 * 	var step : Number = getLocalStep( e );
		 * 
		 * 	// do whatever you want.
		 * }</listing>
		 * 
		 * <p>Basically the returned value is the <code>RTEvent.getStep()</code> return
		 * multiplied by the <code>_nLocalSpeed</code> value of the object.
		 * If the passed-in event isn't a <code>RTEvent</code> the function failed
		 * and throw a <code>TypeError</code>.</p>
		 * 
		 * @param 	e	<code>Event</code> passed by the <code>RTBeacon</code> in the
		 * 				<code>onTick</code> event. It's not necessary to cas
		 * 				the event as a <code>RTEvent</code>, the method will
		 * 				do it automatically.
		 * @return 	the timestep in the local time scale.
		 * @throws	<code>TypeError</code> — The passed-in <code>Event</code> isn't a valid
		 * 			<code>RTEvent</code> instance. 
		 */
		protected function getLocalStep ( e : Event ) : Number
		{
			_checkEventType( e );
			return (e as RTEvent).getStep() * _nLocalSpeed;
		}
		
		/**
		 * Returns the time step in seconds passed in the <code>onTick</code>
		 * events in local timescale.
		 * 
		 * <p>Use that method in the <code>onTick</code> method to automatically
		 * convert the time step from global to local time scale. The example below show
		 * how simple it is to get the real step value to use in your equations.</p>
		 * 
		 * <listing>public override function onTick ( e : Event = null ) : void
		 * {
		 * 	var stepInSecond : Number = getLocalStepInSecond( e );
		 * 
		 * 	// do whatever you want.
		 * }</listing>
		 * 
		 * <p>Basically the returned value is the <code>RTEvent.getStepInSecond()</code>
		 * return multiplied by the <code>_nLocalSpeed</code> value of the object.
		 * If the passed-in event isn't a <code>RTEvent</code> the function failed
		 * and throw a <code>TypeError</code>.</p>
		 * 
		 * @param 	e	<code>Event</code> passed by the <code>RTBeacon</code> in the
		 * 				<code>onTick</code> event. It's not necessary to cas
		 * 				the event as a <code>RTEvent</code>, the method will
		 * 				do it automatically.
		 * @return 	the timestep in the local time scale.
		 * @throws	<code>TypeError</code> — The passed-in <code>Event</code> isn't a valid
		 * 			<code>RTEvent</code> instance.
		 */
		protected function getLocalStepInSecond ( e : Event ) : Number
		{
			_checkEventType( e );
			return (e as RTEvent).getStepInSecond() * _nLocalSpeed;
		}
		
		/**
		 * Check if the passed-in event is a valid RTEvent instance.
		 */
		private function _checkEventType ( e : Event ) : void
		{
			if( !( e is RTEvent ) )
			{
				KairosDebug.ERROR( e + " isn't a RTEvent object, " + this + ".getLocalStepInSecond() failed." );
				throw new TypeError ( e + " isn't a RTEvent object, " + this + ".getLocalStepInSecond() failed." );
			}
		}
		
	}
}
